#include <bits/stdc++.h>
using namespace std;
int n;
int h[1000005];
int heap_size = 0;
void push_up(int x)
{
	while (x > 1) //当x<=1时,说明这个数已经到达了顶点,不必再前进了
	{
		if (h[x / 2] > h[x]) //如果父节点的值比儿子大了,那么不符合小根堆的性质,调整
		{
			swap(h[x / 2], h[x]);
			x = x / 2; //继续向上判断
		}
		else
			break; //不必继续,跳出循环
	}
}
void push_down(int x)
{
	while (x * 2 <= heap_size) //由于堆是完全二叉树,即有可能没有右儿子,所以一个点如果连左儿子都没有就是叶子节点
	{
		int y = x * 2;
		if (y < heap_size && h[y + 1] < h[y]) //找左右儿子中最小的一个且不越界
			y++;
		if (h[y] < h[x])
		{
			swap(h[x], h[y]);
			x = y;
		}
		else
			break;
	}
}
void del(int x)
{
	h[x] = h[heap_size]; //将目标点用最后一个值覆盖
	heap_size--;		 //删去最后一个点
	if (h[x / 2] > h[x])
		push_up(x); //如果调整过后的点比父亲小,则将此点向上调整
	else if (h[x * 2] < h[x] || h[x * 2 + 1] < h[x])
		push_down(x); //如果调整过后的点大于儿子,则将此点向下调整
}
int main()
{
	scanf("%d", &n);
	for (int i = 1; i <= n; i++)
	{
		int opt;
		scanf("%d", &opt);
		if (opt == 1)
		{
			int num;
			scanf("%d", &num);
			h[++heap_size] = num;
			push_up(heap_size);
		}
		if (opt == 2)
		{
			printf("%d\n", h[1]);
		}
		if (opt == 3)
		{
			del(1);
		}
	}
	return 0;
}